package com.fengwk.support.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;
import java.util.zip.CheckedOutputStream;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import com.fengwk.support.exception.BaseException;
import com.fengwk.support.util.LoggerUtils.Logger;

/**
 * 压缩工具</br>
 * 参考博客:</br>
 * http://snowolf.iteye.com/blog/465433</br>
 * http://snowolf.iteye.com/blog/642298</br>
 * http://snowolf.iteye.com/blog/642492</br>
 * http://snowolf.iteye.com/blog/643010</br>
 * http://snowolf.iteye.com/blog/643443</br>
 * http://snowolf.iteye.com/blog/644591</br>
 * http://snowolf.iteye.com/blog/648652
 * 
 * @author fengwk
 *
 */
public class CompressUtils {

	private static final Logger LOG = LoggerUtils.getLogger(CompressUtils.class); 
	
	/**
	 * 用于路径分割,在压缩中应当使用这个标志来分割文件
	 */
	 private static final String PATH = "/"; 
	
	 /**
	  * 空白字符
	  */
    private static final String BLANK = "";  
	
    private CompressUtils() {
		throw new BaseException("can not instance " + getClass());
	}
    
	/**
	 * zlib压缩</br>
	 * 压缩后输入流自动关闭
	 * 
	 * @param is
	 * @return
	 */
	public static byte[] zlib(InputStream is) {
		if (is == null)
			throw new BaseException("is == null");
		return IoUtils.toBytes(os -> {
			try {
				zlib(is, os);
			} catch (IOException e) {
				LOG.error(e);
			}
		});
	}
	

	/**
	 * zlib压缩</br>
	 * 压缩后输入流自动关闭,不对输出流负责
	 * 
	 * @param is
	 * @param os
	 * @throws IOException
	 */
	public static void zlib(InputStream is, OutputStream os) throws IOException {
		if (is == null)
			throw new BaseException("is == null");
		if (os == null) 
			throw new BaseException("os == null");
		DeflaterOutputStream dos = new DeflaterOutputStream(os);
		IoUtils.copy(is, dos);
		dos.finish();
		dos.flush();
		IoUtils.close(dos);
	}

	/**
	 * zlib解压缩</br>
	 * 解压后输入流自动关闭
	 * 
	 * @param is
	 * @return
	 */
	public static byte[] unzlib(InputStream is) {
		if (is == null)
			throw new BaseException("is == null");
		return IoUtils.toBytes(os -> {
			try {
				unzlib(is, os);
			} catch (IOException e) {
				LOG.error(e);
			}
		});
	}

	/**
	 * zlib解压缩</br>
	 * 解压后输入流自动关闭,不对输出流负责
	 * 
	 * @param is
	 * @param os
	 * @throws IOException
	 */
	public static void unzlib(InputStream is, OutputStream os) throws IOException {
		InflaterInputStream iis = new InflaterInputStream(is);
		IoUtils.copy(iis, os);
		IoUtils.close(iis);
	}
	
	/**
	 * zip压缩
	 * 
	 * @param file
	 * @return
	 */
	public static byte[] zip(File file) {
		FileUtils.assertExists(file);
		Map<String, InputStream> isMap = new HashMap<>();
		file2isMap(file, BLANK, isMap);
		return zip(isMap);
	}
	
	/**
	 * zip压缩</br>
	 * 压缩后输入流自动关闭
	 * 
	 * @param isMap
	 * @return
	 */
	public static byte[] zip(Map<String, InputStream> isMap) {
		return IoUtils.toBytes(os -> {
			try {
				zip(isMap, os);
			} catch (IOException e) {
				LOG.error(e);
			}
		});
	}
	
	/**
	 * zip压缩</br>
	 * 压缩后输入流自动关闭,不负责输出流的关闭
	 * 
	 * @param isMap
	 * @param os
	 * @throws IOException
	 */
	public static void zip(Map<String, InputStream> isMap, OutputStream os) throws IOException {
		if (isMap == null)
			throw new BaseException("isMap == null");
		if (os == null)
			throw new BaseException("os == null");
		// 对输出文件做CRC32校验  
        CheckedOutputStream cos = new CheckedOutputStream(os, new CRC32());
        // 输出的zip压缩流
		ZipOutputStream zos = new ZipOutputStream(cos);
		Iterator<Entry<String, InputStream>> it = isMap.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String, InputStream> entry = it.next();
			String path = entry.getKey();
			InputStream is = entry.getValue();
			try {
				if (path == null)
					throw new BaseException("path == null");
				ZipEntry ze = new ZipEntry(path);
				zos.putNextEntry(ze);
				if (is != null)
					IoUtils.copy(is, zos);
				zos.closeEntry();
			} finally {
				IoUtils.close(is);
			}
		}
	}
	
	/**
	 * zip解压缩</br>
	 * 解压后输入流自动关闭
	 * 
	 * @param is
	 * @param outFile 目标文件夹
	 * @throws IOException
	 */
	public static void unzip(InputStream is, File outFile) throws IOException {
		FileUtils.assertExists(outFile);
		if (!outFile.isDirectory())
			throw new BaseException("!outFile.isDirectory()");
		CheckedInputStream cis = new CheckedInputStream(is, new CRC32());
		ZipInputStream zis = new ZipInputStream(cis);
		ZipEntry ze = null;
		String prePath = outFile.getAbsolutePath();
		try {
			while ((ze = zis.getNextEntry()) != null) {
				String filePath = prePath + ze.getName();
				if (ze.isDirectory()) 
					FileUtils.buildFile(filePath, true).mkdirs();
				else {
					OutputStream os = IoUtils.buildOutputStream(filePath);
					try {
						IoUtils.copy(zis, os);
					} finally {
						IoUtils.close(os);
					}
				}
				zis.closeEntry();
			}
		} finally {
			IoUtils.close(zis);
		}
	}
	
	/**
	 * gzip压缩</br>
	 * 压缩后输入流自动关闭
	 * 
	 * @param is
	 * @return
	 * @throws IOException
	 */
	public static byte[] gzip(InputStream is) throws IOException {
		if (is == null)
			throw new BaseException("is == null");
		return IoUtils.toBytes(os -> {
			try {
				gzip(is, os);
			} catch (IOException e) {
				LOG.error(e);
			}
		});
	}
	
	/**
	 * gzip压缩</br>
	 * 压缩后输入流自动关闭,不对输出流负责
	 * 
	 * @param is
	 * @param os
	 * @throws IOException
	 */
	public static void gzip(InputStream is, OutputStream os) throws IOException {
		GZIPOutputStream gzipos = new GZIPOutputStream(os, IoUtils.DEFAULT_BUFFER_SIZE);
		try {
			IoUtils.copy(is, gzipos);
		} finally {
			IoUtils.close(gzipos);
		}
	}
	
	/**
	 * gzip解压</br>
	 * 解压后输入流自动关闭
	 * 
	 * @param is
	 * @return
	 * @throws IOException
	 */
	public static byte[] ungzip(InputStream is) throws IOException {
		if (is == null)
			throw new BaseException("is == null");
		return IoUtils.toBytes(os -> {
			try {
				ungzip(is, os);
			} catch (IOException e) {
				LOG.error(e);
			}
		});
	}
	
	/**
	 * gzip解压</br>
	 * 解压后输入流自动关闭,不对输出流负责
	 * 
	 * @param is
	 * @param os
	 * @throws IOException
	 */
	public static void ungzip(InputStream is, OutputStream os) throws IOException {
		GZIPInputStream gzipis = new GZIPInputStream(is, IoUtils.DEFAULT_BUFFER_SIZE);
		try {
			IoUtils.copy(gzipis, os);
		} finally {
			IoUtils.close(gzipis);
		}
	}
	
	// 文件转输入流Map
	// 若InputStream最后层级的文件夹,则使用null来表示
	private static void file2isMap(File file, String rPath, Map<String, InputStream> isMap) {
		String current = rPath + file.getName();
		if (file.isDirectory()) {
			File[] fs = file.listFiles();
			if (ArrayUtils.isNotBlank(fs)) {
				for (File f: fs) 
					file2isMap(f, current + PATH, isMap);
			} else 
				// 最后的空文件
				isMap.put(current + PATH, null);
		} else 
			isMap.put(current, IoUtils.buildInputStream(file));
	}

}
